// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包fs定义了文件系统的基本接口。
// 文件系统可以由主机操作系统
// 提供，也可以由其他软件包提供。
package fs

import (
	"internal/oserror"
	"time"
	"unicode/utf8"
)

// FS提供对分层文件系统的访问。
// 
// FS接口是文件系统所需的最低实现。
// 文件系统可以实现额外的接口，例如ReadFileFS，以提供额外或优化的功能。
type FS interface {
	// 打开打开命名文件。
	// 
	// 当Open返回错误时，它应该是*PathError 
	// 类型，Op字段设置为“Open”，Path字段设置为name，
	// 以及描述问题的Err字段。
	// 
	// Open应拒绝尝试打开不满足
	// ValidPath（名称）的名称，返回一个*路径错误，错误设置为
	// ErrInvalid或ErrNotExist。
	Open(name string) (File, error)
}

// ValidPath报告给定的路径名
// 是否有效，可用于打开调用。
// 
// 传递给open的路径名是UTF-8编码的、
// 无根的、以斜杠分隔的路径元素序列，如“x/y/z”。
// 路径名不能包含“.”元素或“.”或者空字符串
// 除了根目录名为“”的特殊情况。
// 路径不能以斜杠开头或结尾：“/x”和“x/”无效。
// 
// 请注意，在所有系统上，甚至在Windows上，路径都是用斜杠分隔的。
// 包含其他字符（如反斜杠和冒号）的路径
// 被接受为有效，但这些字符决不能被FS实现解释为路径元素分隔符。
func ValidPath(name string) bool {
	if !utf8.ValidString(name) {
		return false
	}

	if name == "." {
		// 特殊情况
		return true
	}

	// 迭代名称中的元素，检查每个元素。
	for {
		i := 0
		for i < len(name) && name[i] != '/' {
			i++
		}
		elem := name[:i]
		if elem == "" || elem == "." || elem == ".." {
			return false
		}
		if i == len(name) {
			return true // 结尾清晰
		}
		name = name[i+1:]
	}
}

// 一个文件提供对单个文件的访问。
// 文件接口是文件所需的最低实现。
// 目录文件也应该实现ReadDirFile。
// 一个文件可以实现io。读取器或io。作为优化的搜索引擎。
type File interface {
	Stat() (FileInfo, error)
	Read([]byte) (int, error)
	Close() error
}

// DirEntry是从目录
// （使用ReadDir函数或ReadDirFile的ReadDir方法）读取的条目。
type DirEntry interface {
	// Name返回条目描述的文件（或子目录）的名称。
	// 此名称只是路径（基名称）的最后一个元素，而不是整个路径。
	// 例如，Name将返回“hello.go”而不是“home/gopher/hello.go”。
	Name() string

	// IsDir报告条目是否描述了目录。
	IsDir() bool

	// Type返回条目的类型位。
	// 类型位是FileMode返回的常用FileMode位的子集。键入方法。
	Type() FileMode

	// Info返回条目描述的文件或子目录的FileInfo。
	// 返回的文件信息可能来自原始目录读取时
	// 或者来自调用Info时。如果文件在目录读取后已被删除或重命名为
	// ，Info可能会返回符合错误的错误。是（呃，呃不存在）。
	// 如果条目表示符号链接，Info将报告有关链接本身的信息，而不是链接的目标。
	Info() (FileInfo, error)
}

// ReadDirFile是一个目录文件，其条目可以用ReadDir方法读取。
// 每个目录文件都应该实现这个接口。
// （允许任何文件实现此接口，
// ，但如果是这样，ReadDir应为非目录返回一个错误。）
type ReadDirFile interface {
	File

	// ReadDir读取目录的内容，并按目录顺序返回最多n个DirEntry值的片段。
	// 对同一文件的后续调用将产生更多的DirEntry值。
	// 
	// 如果n>0，ReadDir最多返回n个DirEntry结构。
	// 在这种情况下，如果ReadDir返回一个空切片，它将返回
	// 解释原因的非零错误。
	// 在目录的末尾，错误是io。EOF。
	// 
	// 如果n<=0，ReadDir将在一个片中返回目录
	// 中的所有DirEntry值。在这种情况下，如果ReadDir成功（一直读到
	// 目录的末尾），它将返回切片和一个nil错误。
	// 如果在目录结尾之前遇到错误，
	// ReadDir返回在该点之前读取的目录项列表，并返回非零错误。
	ReadDir(n int) ([]DirEntry, error)
}

// 一般文件系统错误。
// 文件系统返回的错误可以针对这些错误进行测试
// 使用错误。是
var (
	ErrInvalid    = errInvalid()    // “无效参数”
	ErrPermission = errPermission() // “权限被拒绝”
	ErrExist      = errExist()      // “文件已存在”
	ErrNotExist   = errNotExist()   // “文件不存在”
	ErrClosed     = errClosed()     // “文件已关闭”
)

func errInvalid() error    { return oserror.ErrInvalid }
func errPermission() error { return oserror.ErrPermission }
func errExist() error      { return oserror.ErrExist }
func errNotExist() error   { return oserror.ErrNotExist }
func errClosed() error     { return oserror.ErrClosed }

// “文件信息”描述了一个文件，并由Stat返回。
type FileInfo interface {
	Name() string       // /文件的基本名称
	Mode() FileMode     // 文件模式位
	ModTime() time.Time // 修改时间
	IsDir() bool        // 模式的缩写（）。IsDir（）
	Sys() any           // 基础数据源（可以返回零）
}

// 文件模式表示文件的模式和权限位。
// 这些位在所有系统上都有相同的定义，因此
// 有关文件的信息可以通过可移植的方式从一个系统移动到另一个系统。并非所有位都适用于所有系统。
// 唯一需要的位是目录的ModeDir。
type FileMode uint32

// 定义的文件模式位是文件模式的最高有效位。
// 九个最低有效位是标准的Unix RWX权限。
// 这些位的值应被视为公共API的一部分，
// 可用于有线协议或磁盘表示：它们不得被
// 更改，尽管可能会添加新位。
const (
	// 单个字母是字符串方法格式化所使用的缩写
	// 。
	ModeDir        FileMode = 1 << (32 - 1 - iota) // d:是一个目录
	ModeAppend                                     // a:只追加
	ModeExclusive                                  // l:专用
	ModeTemporary                                  // T:临时文件；仅计划9 
	ModeSymlink                                    // L:符号链接
	ModeDevice                                     // D:设备文件
	ModeNamedPipe                                  // p:命名管道（FIFO）
	ModeSocket                                     // S:Unix域套接字
	ModeSetuid                                     // u:setuid 
	ModeSetgid                                     // g:setgid 
	ModeCharDevice                                 // c:Unix字符设备，当ModeDevice设置
	ModeSticky                                     // t:Stick 

	// 类型位的掩码，我们一无所知。对于常规文件，将不设置任何文件。
	ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

	ModePerm FileMode = 0777 // Unix权限位
)

func (m FileMode) String() string {
	const str = "dalTLDpSugct?"
	var buf [32]byte // 模式为uint32。
	w := 0
	for i, c := range str {
		if m&(1<<uint(32-1-i)) != 0 {
			buf[w] = byte(c)
			w++
		}
	}
	if w == 0 {
		buf[w] = '-'
		w++
	}
	const rwx = "rwxrwxrwx"
	for i, c := range rwx {
		if m&(1<<uint(9-1-i)) != 0 {
			buf[w] = byte(c)
		} else {
			buf[w] = '-'
		}
		w++
	}
	return string(buf[:w])
}

// IsDir报告m是否描述目录。
// 也就是说，它测试m中设置的ModeDir位。
func (m FileMode) IsDir() bool {
	return m&ModeDir != 0
}

// 是常规报告m是否描述常规文件。
// 也就是说，它测试没有设置模式类型位。
func (m FileMode) IsRegular() bool {
	return m&ModeType == 0
}

// Perm以m（m&ModePerm）返回Unix权限位。
func (m FileMode) Perm() FileMode {
	return m & ModePerm
}

// Type返回m中的类型位（m&ModeType）。
func (m FileMode) Type() FileMode {
	return m & ModeType
}

// PathError记录一个错误以及导致该错误的操作和文件路径。
type PathError struct {
	Op   string
	Path string
	Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

func (e *PathError) Unwrap() error { return e.Err }

// 超时报告此错误是否表示超时。
func (e *PathError) Timeout() bool {
	t, ok := e.Err.(interface{ Timeout() bool })
	return ok && t.Timeout()
}
